二分:傳送門
三分:傳送門
(注意,是五舍六入,不是四捨五入,在2018年10月23日前是這樣的)
話說一本通上不是有講嘛,做法自己看吧。。。(但是我太弱了,精度版看不懂QWQ)。
簡單講一下二分與三分吧。
二分:必須滿足單調性:
非增或非減就叫單調性(如果就好幾個數相同,一般會用二分來找第一個數或最後一個數)。
我們用兩個數字l與r來代表搜索範圍,而mid代表中間的位置的值,來跳來跳去,看情況來寫。
如這題
int l=1,r=n,mid,ans=0;
while(l<=r)
{
mid=(l+r)/2;
if(a[mid]<=m)l=mid+1,ans=mid;
else if(a[mid]>m)r=mid-1;
}
printf("%d\n",ans);
當然,還可以用二分來二分答案,比如你要用某種方法,但是缺少一種條件,你又意外發現這個條件越大或越小,會讓你方法越容易達到目標(也就是發現了二分性),就可以二分這個條件,不斷丟給這個方法,讓他運行。
如這題
//發現這道題如果二分總和,總和越大,分的段數就越少,越容易小於等於m,也就越容易達到目標,因此得出做法
#include<cstdio>
#include<cstring>
using namespace std;
int a[210000],b[210000],n,m;
bool pd(int x)
{
int kk=1/*自行理解*/,ans=0;
for(int i=1;i<=n;i++)
{
if(a[i]>x)return false;//如果有一個數超過了,就退出
if(ans+a[i]<=x)ans+=a[i];//加上
else
{
kk++;/*統計答案*/if(kk>m)return false;//分的段數過多,退出
ans=a[i];//重置
}
}
return true;
}
int main()
{
int sum=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),sum+=a[i];
int l=1/*這裏可以改成a數組的最大值)*/,r=sum/*統計所有的和*/,mid,x;
while(l<=r)
{
mid=(l+r)/2;
if(pd(mid)==true)//如果可以,代表可以讓r再收攏一點
{
r=mid-1;x=mid;
}
else l=mid+1;//不行,則擴寬l的限制
}
printf("%d\n",x);
return 0;
}
(我不是標題狗)
但是開頭的那道二分題,是要用精度的!!!
看別人的代碼,也都是大把大把的double,醜陋的代碼:
#include<cstdio>
#include<cstring>
using namespace std;
double a[210000],sum[210000],b[210000];
int n,L;
double mymin(double x,double y){return x<y?x:y;}
double mymax(double x,double y){return x>y?x:y;}
bool check(double x)//這個上網搜搜都是有的
{
double min_val=999999999.0,ans=-99999999.0;
for(int i=1;i<=n;i++)b[i]=a[i]-x,sum[i]=b[i]+sum[i-1];
for(int i=L;i<=n;i++)//用DP搜索一段長度大於等於L的子串的最大和
{
min_val=mymin(min_val,sum[i-L]);
ans=mymax(ans,sum[i]-min_val);
}
return ans>=0.0;//猴子已經死機。。。上網搜吧。。。
}
int main()
{
scanf("%d%d",&n,&L);
for(int i=1;i<=n;i++)scanf("%lf",&a[i]);
double l=-1e6,r=1e6,mid,ans=0.0,jie=1e-5;//猴子已死機。。。
while(r-l>jie)
{
mid=(l+r)/2;
if(check(mid))ans=mid,l=mid;
else r=mid;
}
printf("%d\n",int(r*1000.0));
return 0;
}
還是上網弄了一個下來
這題可以用斜率優化做也可以用二分做,我用的是二分做法。
題意:給你n個牛的自身價值,讓你找出連續的且數量大於等於F的一段區間,使這段區間內的牛的平均價值最大。
思路:用二分枚舉平均值ave,每個牛的價值都減去ave,看是否有連續的超過f長度的區間使得這段區間的價值大於等於0,如果能找到,那麼說明這個平均值可以達到。先每個a[i]減去ave得到b[i],用dp[i]表示以i爲結尾區間連續長度大於等於f的最大連續區間和,maxx[i]表示以i爲結尾的最大連續區間和,sum[i]表示1~i的價值總和那麼maxx[i]=max(maxx[i-1]+b[i],b[i]),dp[i]=maxx[i-f+1]+sum[i]-sum[i-f+1],判斷是否有一個i(i>=f)滿足dp[i]>=0.
作者:Herumw
來源:CSDN
原文:https://blog.csdn.net/kirito_acmer/article/details/48716719
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!
如果你是神犇看懂了=_=。哥哥,我們不約
好的,如果你不是神犇,那麼請:
不過,有時,我們不一定要用double,我們乘以一個1000轉成int,然後在check再暫時轉回double
代碼(摘自我機房大佬CLB的代碼):
//Sorry,之前放錯代碼了
#include<cstdio>
#include<cstring>
#define INF 2147483647
using namespace std;
int N,L,a[110000];
long long sum[110000];
inline bool check(int x)
{
for(int i=1;i<=N;i++) sum[i]=sum[i-1]+(a[i]-x);//減去平均值,看看總和是否能爲非負數
long long minn=INF;
for(int i=L;i<=N;i++)
{
if(sum[i-L]<minn) minn=sum[i-L];
if(sum[i]-minn>=0) return true;//這裏是把拖後腿那一部分減掉,如果爲正數,就表示此方案可行
}
return false;//此方案不行
}
int main()
{
scanf("%d%d",&N,&L);
for(int i=1;i<=N;i++) scanf("%d",&a[i]),a[i]*=1000;//最後結果要乘1000,爲了方便計算(不算小數),就在開始直接乘了
int l,r,mid,ans;
l=0;r=2000000;
while(l<=r)//二分平均值
{
mid=(l+r)>>1;
if(check(mid)==true) l=mid+1,ans=mid;
else r=mid-1;
}
printf("%d\n",ans);
return 0;
}
到三分了,三分呢,主要解決單峯問題(求單峯),不過遞增或遞減也可以喲不過只是求第一個數或最後一個數!
所謂三分,肯定是把區間用兩個數(記作m1與m2,m1<=m2)分成三個部分(除了某些特殊情況:n=2…),然後將這兩個數比較,然後l跳到m1+1,r跳到m2-1,然後當l>r退出,由於這裏比二分還複雜,所以猴子還沒找到直接記錄答案的方法,只能最後比較l與r,雖然一次只縮小
但是總比某退火的玄學複雜度快吧。
舉個栗子(二次函數:開口向上):
以x軸做三分,那麼m1(A點)的y小於m2(B點)的y,那麼我們就讓r跳到比m2小一點的地方(按題目來定),反之讓l跳到比m1大一點點的位置,來達到我們將l與r縮小的目的。
至於突然退化成一次函數的二次函數(某毒瘤出題人乾的),與二次函數的情況一樣,不過l或r有一個不變罷了。。。
僞例題:
一個序列,其中有一個數,這個數左邊的序列嚴格遞增,左邊嚴格遞減,右邊嚴格遞增。
一個整數n
n個數字
輸出這個數字:
樣例輸入:
5
1 2 3 2 1
樣例輸出:
3
int l=1,r=n,m1,m2;
while(l<=r)
{
m1=l+(r-l+1/*+1不+1都可以*/)/3/*爲什麼不+1?如果l==r,m1就跳出去了,m2也是同理*/;m2=r-(r-l+1)/3;
if(a[m1]<=a[m2]/*<與<=都可以*/)r=m2-1;
else l=m1+1;//縮小範圍
}
if(a[l]<a[r])printf("%d\n",a[l]);
else printf("%d\n",a[r]);
至於如果有一段的值相等,這種情況我認爲是可以的,歡迎大家再我的下方評論,畢竟三分剛學不久。。。
比如上圖。。。
然後,又到了開頭的那道三分了。。。
又是精度問題!
至於單峯性。。。
題目:給你n條開口向上的二次曲線Si(a>0),定義F(x) = max(Si(x)),求F(x)的最小值。
分析:三分。F(x)是一個單峯函數,先單調遞減後單調遞增,利用三分求最小值。
首先,證明兩個二次函數構造的F2(x)爲單峯函數;
(如果不成立,則存在兩個連續的波谷,交點處一個函數遞增另一個遞減,矛盾,不會取遞減函數)
然後,用數學歸納法證明,Fi(x)爲單峯函數,則Fi+1 = max(Fi(x),Si+1(x))也是單峯函數;
(如果存在兩個(或更多)連續的波谷,交點處一個函數遞增另一個遞減,矛盾,所以只有一個波谷)
結論,綜上所述得證結論,只存在一個波谷。
作者:小白菜又菜
來源:CSDN
原文:https://blog.csdn.net/mobius_strip/article/details/45618095
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!
看不懂自己YY吧,啊啊啊啊。
難道又要動用我們毒瘤可愛的double了?
不,我拒絕!!!
既然保留四位小數,又五舍六入,那麼乘100000啦!
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
ll a[200000],b[200000],c[200000],n;
inline double mymax(double x,double y){return x>y?x:y;}
inline double cai(ll x)//從所有函數中選最大值
{
double xx=x/100000.0;//變回double
double mmax=-999999999;
for(ll i=1;i<=n;i++)mmax=mymax(mmax,(a[i]*1.0)*xx*xx+(b[i]*1.0)*xx+(c[i]*1.0));
return mmax;
}
int main()
{
ll T;scanf("%lld",&T);
while(T--)
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
ll l=0,r=1e8/*1*10的8
次方*/,m1,m2,ans;//三分日常操作
while(l<=r)
{
m1=l+(r-l+1)/3;m2=r-(r-l+1)/3;
if(cai(m1)<=cai(m2))r=m2-1;
else l=m1+1;
}
if(cai(l)<cai(r))printf("%.4lf\n",cai(l));
else printf("%.4lf\n",cai(r));//統計答案
}
return 0;
}
。。。
作者可能學了個假的三分。。。
不過,如果乘以一百萬(多乘了個10)。。。
猴子(作者)想了一個壞想法,於是我用了高精度1e10與1e11,終於,在1e11時卡精度AC了!
AC代碼:
//猴子將double*1e8將他轉爲long long
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
ll a[200000],b[200000],c[200000],n;
inline double mymax(double x,double y){return x>y?x:y;}
inline double cai(ll x)//轉回long long;
{
double xx=x/100000000.0;
double mmax=-999999999;
for(ll i=1;i<=n;i++)mmax=mymax(mmax,(a[i]*1.0)*xx*xx+(b[i]*1.0)*xx+(c[i]*1.0));
return mmax;
}
int main()
{
ll T;scanf("%lld",&T);
while(T--)
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
ll l=0,r=1e11,m1,m2,ans;
while(l<=r)
{
m1=l+(r-l+1)/3;m2=r-(r-l+1)/3;
if(cai(m1)<cai(m2))r=m2-1;
else l=m1+1;//三分
}
if(cai(l)<cai(r))printf("%.4lf\n",cai(l));
else printf("%.4lf\n",cai(r));
}
return 0;
}
所以,帶精度的二分與三分是可以轉成long long來做的,在check裏再轉會double就行了,不過三分可能要多乘一點。
終於寫完了。
每日笑話:
追到我的女神 我用了三個辦法 辦法一 堅持 辦法二 不要臉 辦法三 堅持不要臉 她帶我回家 她爸爸很無禮地跟我說 我養了我女兒二十年 我憑什麼把她嫁給你 我回答 你養她二十年 我要養她四十年 還要照顧你三十年 你憑什麼不把她嫁給我
--------來源
--------來源
感謝大家觀看。
就怪了!
難道大家沒發現三分會比二分慢嗎?
啊啊啊啊啊!
但是,我們可以將m1=(l+r)/2;m2=m1+1;
這樣子,如果比較完後,l=m2或者r=m1,正確性的話雖然m1與m2靠的很近,不過依舊可以達到單峯在l與r之間,就對了,不過是l<r不是l<=r,因爲這樣分m1與m2嚴格m1<m2且m1與m2屬於[l,r],所以只能用l<r,不過這樣有個好處,就是由於l或r都是等於m1或m2的,所以不會出現r<l的情況,最後一定l==r,所以答案就是l或r,而且一次縮小的區間變大了,常數也就小了!
代碼:
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
ll a[200000],b[200000],c[200000],n;
inline double mymax(double x,double y){return x>y?x:y;}
inline double cai(ll x)
{
double xx=x/100000000.0;
double mmax=-999999999;
for(ll i=1;i<=n;i++)mmax=mymax(mmax,(a[i]*1.0)*xx*xx+(b[i]*1.0)*xx+(c[i]*1.0));
return mmax;
}
int main()
{
ll T;scanf("%lld",&T);
while(T--)
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
ll l=0,r=1e11,m1,m2,ans;
while(l<r)//常數小的三分
{
m1=(l+r)>>1;m2=m1+1;
if(cai(m1)<cai(m2))r=m1;
else l=m2;
}
printf("%.4lf\n",cai(r/*可以換成l*/));
}
return 0;
}
//開心!
是不是意味着我們可以出毒瘤卡常題目了